package com.hdlang.android.v2.patch.plugin.utils;

import com.hdlang.android.v2.patch.ChangeQuickRedirect;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.GeneratorAdapter;

public class AsmUtils {
    public final static String REDIRECTFIELD_NAME = "changeQuickRedirect";
    public final static String REDIRECTCLASSNAME = Type.getDescriptor(ChangeQuickRedirect.class);
    public final static String PROXYCLASSNAME = "com.hdlang.android.v2.patch.PatchProxy".replace(".", "/");
    public final static String REDIRECT_LOCAL_NAME = "TYPE";
    public final static String REDIRECT_LOCAL_DESCRIPTOR = "Ljava/lang/Class;";

    public static void createInsertCode(
            GeneratorAdapter mv,
            String className,
            Type[] args,
            Type returnType,
            boolean isStatic,
            String methodId
    ) {
        prepareMethodParameters(mv, className, args, returnType, isStatic, methodId);
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                PROXYCLASSNAME,
                "proxy",
                "([Ljava/lang/Object;Ljava/lang/Object;${REDIRECTCLASSNAME}ZLjava/lang/String;)Lcom/hdlang/android/v2/patch/PatchProxyResult;",
                false
        );
        int local = mv.newLocal(Type.getType("Lcom/hdlang/android/v2/patch/PatchProxyResult;"));
        mv.storeLocal(local);
        mv.loadLocal(local);

        Label supportedLabel = new Label();
        mv.visitJumpInsn(Opcodes.IFNULL, supportedLabel);

        mv.loadLocal(local);
        mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                "Lcom/hdlang/android/v2/patch/PatchProxyResult",
                "isSupported",
                "()Z",
                false
        );

        mv.visitJumpInsn(Opcodes.INEG, supportedLabel);

        if ("V" == returnType.getDescriptor()) {
            mv.visitInsn(Opcodes.RETURN);
        } else {
            mv.loadLocal(local);
            mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "Lcom/hdlang/android/v2/patch/PatchProxyResult",
                    "getResult",
                    "()Ljava/lang/Object;",
                    false
            );
            //强制转化类型
            if (!castPrimateToObj(mv, returnType.getDescriptor())) {
                //这里需要注意，如果是数组类型的直接使用即可，如果非数组类型，就得去除前缀了,还有最终是没有结束符;
                //比如：Ljava/lang/String; ==》 java/lang/String
                final int len = returnType.getDescriptor().length();
                String newTypeStr;
                if (returnType.getDescriptor().startsWith("[")) {
                    newTypeStr = returnType.getDescriptor().substring(0, len);
                } else {
                    newTypeStr = returnType.getDescriptor().substring(1, len - 1);
                }
                mv.visitTypeInsn(Opcodes.CHECKCAST, newTypeStr);
            }

            //这里还需要做返回类型不同返回指令也不同
            mv.visitInsn(getReturnTypeCode(returnType.getDescriptor()));
        }
        mv.visitLabel(supportedLabel);
    }


    private static void prepareMethodParameters(
            GeneratorAdapter mv,
            String className,
            Type[] args,
            Type returnType,
            boolean isStatic,
            String methodId
    ) {
        //第一个参数：new Object[]{...};,如果方法没有参数直接传入new Object[0]
        if (args != null && args.length > 0) {
            createObjectArray(mv, args, isStatic);
        } else {
            mv.visitInsn(Opcodes.ICONST_0);
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        }

        //第二个参数：this,如果方法是static的话就直接传入null
        if (isStatic) {
            mv.visitInsn(Opcodes.ACONST_NULL);
        } else {
            mv.visitVarInsn(Opcodes.ALOAD, 0);
        }

        //第三个参数：changeQuickRedirect
        mv.visitFieldInsn(Opcodes.GETSTATIC, className, REDIRECTFIELD_NAME, REDIRECTCLASSNAME);

        //第四个参数：false,标志是否为static
        mv.visitInsn(isStatic ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
        //第五个参数：
        mv.push(methodId);
        //第六个参数：参数class数组
        createClassArray(mv, args);
        //第七个参数：返回值类型class
        createReturnClass(mv, returnType);
    }

    private static void createReturnClass(GeneratorAdapter mv, Type returnType) {
        redirectLocal(mv, returnType);
    }

    private static void createClassArray(GeneratorAdapter mv, Type[] args) {
        // create an array of objects capable of containing all the parameters and optionally the "this"

        createLocals(mv, args);

        final int len = args.length;
        // we need to maintain the stack index when loading parameters from, as for long and double
        // values, it uses 2 stack elements, all others use only 1 stack element.
        for (int arrayIndex = 0; arrayIndex < len; arrayIndex++) {
            Type arg = args[arrayIndex];
            // duplicate the array of objects reference, it will be used to store the value in.
            mv.dup();
            // index in the array of objects to store the boxed parameter.
            mv.push(arrayIndex);
            // Pushes the appropriate local variable on the stack
            redirectLocal(mv, arg);
//			 mv.visitLdcInsn(Type.getType(arg.getDescriptor()));
            // potentially box up intrinsic types.
//			 mv.box(arg);
            mv.arrayStore(Type.getType(Class.class));
            // stack index must progress according to the parameter type we just processed.
//			 stackIndex += arg.getSize();
        }
    }

    /**
     * Creates and pushes to the stack the array to hold all the parameters to redirect, and
     * optionally this.
     */
    private static void createLocals(GeneratorAdapter mv, Type[] args) {
        mv.push(args.length);
        mv.newArray(Type.getType(Class.class));
    }

    private static void redirectLocal(GeneratorAdapter mv, Type type) {
        String localTypeName = "";
        switch (type.getDescriptor()) {
            case "Z":
                localTypeName = "java/lang/Boolean";
                break;
            case "B":
                localTypeName = "java/lang/Byte";
                break;
            case "C":
                localTypeName = "java/lang/Character";
                break;
            case "S":
                localTypeName = "java/lang/Short";
                break;
            case "I":
                localTypeName = "java/lang/Integer";
                break;
            case "F":
                localTypeName = "java/lang/Float";
                break;
            case "D":
                localTypeName = "java/lang/Double";
                break;
            case "J":
                localTypeName = "java/lang/Long";
                break;
            case "V":
                localTypeName = "java/lang/Void";
                break;
        }
        if (localTypeName != null && localTypeName.length() > 0) {
            mv.visitFieldInsn(
                    Opcodes.GETSTATIC,
                    localTypeName,
                    REDIRECT_LOCAL_NAME,
                    REDIRECT_LOCAL_DESCRIPTOR
            );
        } else {
            mv.visitLdcInsn(Type.getType(type.getDescriptor()));
        }
    }

    private static void createObjectArray(
            GeneratorAdapter mv,
            Type[] paramsTypeClass,
            boolean isStatic
    ) {
        //Opcodes.ICONST_0 ~ Opcodes.ICONST_5 这个指令范围
        final int argsCount = paramsTypeClass.length;
        mv.push(argsCount);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

        //如果是static方法，没有this隐含参数
        int loadIndex = isStatic ? 0 : 1;

        //填充数组数据
        for (int i = 0; i < argsCount; i++) {
            mv.visitInsn(Opcodes.DUP);
            mv.push(i);

            //这里又要做特殊处理，在实践过程中发现个问题：public void xxx(long a, boolean b, double c,int d)
            //当一个参数的前面一个参数是long或者是double类型的话，后面参数在使用LOAD指令，加载数据索引值要+1
            //个人猜想是和long，double是8个字节的问题有关系。这里做了处理
            //比如这里的参数：[a=LLOAD 1] [b=ILOAD 3] [c=DLOAD 4] [d=ILOAD 6];
            if (i >= 1) {
                //这里需要判断当前参数的前面一个参数的类型是什么
                String descriptor = paramsTypeClass[i - 1].getDescriptor();
                if ("J".equals(descriptor) || "D".equals(descriptor)) {
                    loadIndex++;
                }
            }
            if (!createPrimateTypeObj(mv, loadIndex, paramsTypeClass[i].getDescriptor())) {
                mv.visitVarInsn(Opcodes.ALOAD, loadIndex);
                mv.visitInsn(Opcodes.AASTORE);
            }
            loadIndex++;
        }
    }

    private static void createBooleanObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Byte");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ILOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Byte", "<init>", "(B)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createShortObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Short");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ILOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Short", "<init>", "(S)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createCharObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Character");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ILOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Character", "<init>", "(C)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createIntegerObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Integer");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ILOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Integer", "<init>", "(I)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createFloatObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Float");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.FLOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Float", "<init>", "(F)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createDoubleObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Double");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.DLOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Double", "<init>", "(D)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    private static void createLongObj(MethodVisitor mv, int position) {
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/Long");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.LLOAD, position);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Long", "<init>", "(J)V");
        mv.visitInsn(Opcodes.AASTORE);
    }

    /**
     * 创建基本类型对应的对象
     *
     * @param mv
     * @param position
     * @param typeDescriptor
     * @return
     */
    private static boolean createPrimateTypeObj(
            MethodVisitor mv,
            int position,
            String typeDescriptor
    ) {
        if ("Z".equals(typeDescriptor) || "B".equals(typeDescriptor)) {
            createBooleanObj(mv, position);
            return true;
        }
        if ("C".equals(typeDescriptor)) {
            createCharObj(mv, position);
            return true;
        }
        if ("S".equals(typeDescriptor)) {
            ;
            createShortObj(mv, position);
            return true;
        }
        if ("I".equals(typeDescriptor)) {
            createIntegerObj(mv, position);
            return true;
        }
        if ("F".equals(typeDescriptor)) {
            createFloatObj(mv, position);
            return true;
        }
        if ("D".equals(typeDescriptor)) {
            createDoubleObj(mv, position);
            return true;
        }
        if ("J".equals(typeDescriptor)) {
            createLongObj(mv, position);
            return true;
        }
        return false;
    }

    /**
     * 基本类型需要做对象类型分装
     *
     * @param typeDescriptor
     * @return
     */
    private static boolean castPrimateToObj(MethodVisitor mv, String typeDescriptor) {
        if ("Z".equals(typeDescriptor)) {
            mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");//强制转化类型
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
        }
        if ("B".equals(typeDescriptor)) {
            mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Byte");//强制转化类型
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
        }
        if ("C".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Character"); //强制转化类型
            mv.visitMethodInsn(
                    AdviceAdapter.INVOKEVIRTUAL,
                    "java/lang/Character",
                    "charValue",
                    "()C"
            );
            return true;
        }
        if ("S".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Short"); //强制转化类型
            mv.visitMethodInsn(AdviceAdapter.INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
            return true;
        }
        if ("I".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Integer"); //强制转化类型
            mv.visitMethodInsn(AdviceAdapter.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
            return true;
        }
        if ("F".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Float"); //强制转化类型
            mv.visitMethodInsn(AdviceAdapter.INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
            return true;
        }
        if ("D".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Double"); //强制转化类型
            mv.visitMethodInsn(
                    AdviceAdapter.INVOKEVIRTUAL,
                    "java/lang/Double",
                    "doubleValue",
                    "()D"
            );
            return true;
        }
        if ("J".equals(typeDescriptor)) {
            mv.visitTypeInsn(AdviceAdapter.CHECKCAST, "java/lang/Long"); //强制转化类型
            mv.visitMethodInsn(AdviceAdapter.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
            return true;
        }
        return false;
    }


    private static int getReturnTypeCode(String typeDescriptor) {
        switch (typeDescriptor) {
            case "Z":
            case "S":
            case "B":
            case "C":
            case "I":
                return AdviceAdapter.IRETURN;
            case "F":
                return AdviceAdapter.FRETURN;
            case "D":
                return AdviceAdapter.DRETURN;
            case "J":
                return AdviceAdapter.LRETURN;
        }
        return
                AdviceAdapter.ARETURN;
    }
}
